SSEG SEGMENT STACK
    STK DB 20 DUP(0)
SSEG ENDS
DSEG SEGMENT
    MP DB 225 DUP(0)
    I DB 0    ;横坐标
    J DB 0    ;纵坐标
    TI DB 0;
    TJ DB 0;暂用变量
    CNTW DW 0;基址偏移的16位量表示
    CNT DB 0;基址偏移的8位量表示
    FINISH DB 0    ;棋局是否完成
    COUNT DB 1    ;连线上的棋子个数
    OVERFLOW DB 0    ;棋盘是否满了 下满225次未有人胜利即后宣布和棋
    NOW DB 0
    STR1 DB 'Game start,either side can win by reaching five pieces on a line.',0DH,0AH,'$'
    STR2 DB '    0  1  2  3  4  5  6  7  8  9 10 11 12 13 14',0DH,0AH,'$'
    STR3 DB 0DH,0AH,'$'
    STR4 DB 'White round, please enter two numbers, separated by spaces,end withs #',0DH,0AH,'$'    ;各个游戏提示语句
    STR5 DB 'Black round, please enter two numbers, separated by spaces,end withs #',0DH,0AH,'$'
    STR6 DB 'The data is wrong, please re-enter it',0DH,0AH,'$'
    STR7 DB 'White win.',0DH,0AH,'$'
    STR8 DB 'Black win.',0DH,0AH,'$'
    STR9 DB 'Game ends,draw.'
DSEG ENDS
CSEG SEGMENT
    ASSUME CS:CSEG,DS:DSEG
    ASSUME SS:SSEG
MAIN PROC
START:    MOV AX,DSEG
    MOV DS,AX
    MOV AX,SSEG
    MOV SS,AX
    MOV SP,LENGTH STK
    MOV DX,OFFSET STR1
    MOV AH,9
    INT 21h        ;显示游戏开始
;这是最开始的外圈循环  白方先手继而黑方

IN_WHITE:        ;白方落子循环                此处黑白落子逻辑判断代码有大部分相似,但是避免跳转逻辑过于复杂因此黑白落子判断分开执行
    CALL SHOW    ;显示棋盘
    MOV TI,0
    MOV TJ,0    ;多一层防止数据错误
    MOV DX,OFFSET STR4    ;白方回合提示
    MOV AH,9
    INT 21h
    MOV AH, 1 ; ah设置成1模式，用于从键盘输入
    INT 21H ; 调用系统中断，输入
    CMP AL,30H
    JB W_NOT
    CMP AL,39H
    JA W_NOT
    JMP NEXTW    ;第一个输入无误
W_NOT:
    MOV DL,0DH
    MOV AH,2
    INT 21H
    MOV DL,0AH        ;显示换行
    MOV AH,2
    INT 21H
    MOV DX,OFFSET STR6
    MOV AH,9
    INT 21h
    JMP IN_WHITE    ;返回白方落子循环处
NEXTW:
    SUB AL, 30H    ; 输入的结果默认是放在al的，减去30h是为了把字符转换成数字
    MOV TI,AL    ;暂存这个量
    MOV AH, 1 ; ah设置成1模式，用于从键盘输入
    INT 21H ; 调用系统中断，输入
    CMP AL,20H
    JE NEXTWW    ;检测到空格 跳到接受第二个数的位置
    CMP AL,30H
    JB W_NOT
    CMP AL,34H
    JA W_NOT
    CMP TI,1
    JA W_NOT    ;超出14的说明输入错误 否则TI为1
    MOV TI,10
    SUB AL, 30H    ; 输入的结果默认是放在al的，减去30h是为了把字符转换成数字
    MOV TJ,AL    ;暂存这个量
NEXTWW:
    MOV BL,TI
    ADD BL,TJ
    MOV I,BL    ;保留行数据
    MOV TI,0
    MOV TJ,0    ;初始化,防止数据遗留出问题
SEC:
    MOV AH, 1 ; ah设置成1模式，用于从键盘输入
    INT 21H ; 调用系统中断，输入
    CMP AL,20H
    JE SEC    ;检测到空格 返回一段
    CMP AL,30H
    JB W_NOT
    CMP AL,39H
follow:    JA W_NOT    ;下面接力，超出跳转位置
    SUB AL, 30H    ; 输入的结果默认是放在al的，减去30h是为了把字符转换成数字
    MOV TI,AL    ;暂存这个量
    MOV AH, 1 ; ah设置成1模式，用于从键盘输入
    INT 21H ; 调用系统中断，输入
    CMP AL,23H    ;#结尾
    JE ENDWW    ;输入结束
    CMP AL,30H
    JB W_NOT
    CMP AL,34H
    JA W_NOT
    CMP TI,1
    JA follow    ;超出14的说明输入错误 否则TI为1
    MOV TI,10    ;转化成10便于操作。。因为只有10-14所以十位只有这个
    SUB AL, 30H    ; 输入的结果默认是放在al的，减去30h是为了把字符转换成数字
    MOV TJ,AL    ;暂存这个量
ENDWW:
    MOV DL,0DH
    MOV AH,2
    INT 21H
    MOV DL,0AH        ;显示换行
    MOV AH,2
    INT 21H
    MOV BL,TI
    ADD BL,TJ
    MOV J,BL    ;保留列数据
    MOV TI,0
    MOV TJ,0    ;初始化,防止数据遗留出问题
    ;获得相对MP的8位/16位的偏移量
    MOV AL,I    ;行数*15+列数才是当前元素的相对于MP的偏移量
    MOV CL,15
    MUL CL
    MOV CNTW,AX
    MOV DL,BYTE PTR CNTW
    ADD DL,J
    MOV CNT,DL    ;保留相对于MP的偏移量 为byte
    MOV AX,WORD PTR CNT
    MOV CNTW,AX    ;保留相对于MP的偏移量 为Word
    MOV BX,OFFSET MP
    ADD BX,CNTW
    MOV AL,[BX]    ;取到MP当前玩家准备落子的坐标
    CMP AL,0    ;此处是否没有落子
    JNE follow    ;不等于0表示此处有落子 该次落子失败,需要重新选择落子处
    ;成功落子,改变MP的坐标数据
    ADD OVERFLOW,1    ;落子+1
    MOV BYTE PTR[BX],27H    ;以W代表白棋  B代表黑棋        因为棋盘输出时每个数字自动加30H转为字符，所以此处的W ASII码先减去30H
    CALL WIN;调用判断获胜与否的子程序
    CMP FINISH,1
    JE EXIT_TEMP        ;获胜条件已达成

IN_BLACK:        ;黑方落子循环
    CALL SHOW    ;显示棋盘
    MOV TI,0
    MOV TJ,0    ;多一层防止数据错误
    MOV DX,OFFSET STR5    ;黑方回合提示
    MOV AH,9
    INT 21h
    MOV AH, 1 ; ah设置成1模式，用于从键盘输入
    INT 21H ; 调用系统中断，输入
    CMP AL,30H
    JB B_NOT
    CMP AL,39H
    JA B_NOT
    JMP NEXTB    ;第一个输入无误
B_NOT:
    MOV DL,0DH
    MOV AH,2
    INT 21H
    MOV DL,0AH        ;显示换行
    MOV AH,2
    INT 21H
    MOV DX,OFFSET STR6
    MOV AH,9
    INT 21h
    JMP IN_BLACK    ;返回黑方落子循环处
;中转处
EXIT_TEMP:
    CMP FINISH,1
    JE EXIT_TEMPP        ;获胜条件已达成
NEXTB:
    SUB AL, 30H    ; 输入的结果默认是放在al的，减去30h是为了把字符转换成数字
    MOV TI,AL    ;暂存这个量
    MOV AH, 1 ; ah设置成1模式，用于从键盘输入
    INT 21H ; 调用系统中断，输入
    CMP AL,20H
    JE NEXTBB    ;检测到空格 跳到接受第二个数的位置
    CMP AL,30H
    JB B_NOT
    CMP AL,34H
    JA B_NOT
    CMP TI,1
    JA B_NOT    ;超出14的说明输入错误 否则TI为1
    MOV TI,10
    SUB AL, 30H    ; 输入的结果默认是放在al的，减去30h是为了把字符转换成数字
    MOV TJ,AL    ;暂存这个量
NEXTBB:
    MOV BL,TI
    ADD BL,TJ
    MOV I,BL    ;保留行数据
    MOV TI,0
    MOV TJ,0    ;初始化,防止数据遗留出问题
THR:
    MOV AH, 1 ; ah设置成1模式，用于从键盘输入
    INT 21H ; 调用系统中断，输入
    CMP AL,20H
    JE THR    ;检测到空格 返回一段
    CMP AL,30H
    JB B_NOT
    CMP AL,39H
folloB:    JA B_NOT    ;下面接力，超出跳转位置
    SUB AL, 30H    ; 输入的结果默认是放在al的，减去30h是为了把字符转换成数字
    MOV TI,AL    ;暂存这个量
    MOV AH, 1 ; ah设置成1模式，用于从键盘输入
    INT 21H ; 调用系统中断，输入
    CMP AL,23H    ;#结尾
    JE ENDBB    ;输入结束
    CMP AL,30H
    JB B_NOT
    CMP AL,34H
    JA B_NOT
    CMP TI,1
    JA folloB    ;超出14的说明输入错误 否则TI为1
    MOV TI,10    ;转化成10便于操作。。因为只有10-14所以十位只有这个
    SUB AL, 30H    ; 输入的结果默认是放在al的，减去30h是为了把字符转换成数字
    MOV TJ,AL    ;暂存这个量
    JMP ENDBB    ;后面安排一个中转处
;中转处
EXIT_TEMPP:
    CMP FINISH,1
    JE EXIT        ;获胜条件已达成
ENDBB:
    MOV DL,0DH
    MOV AH,2
    INT 21H
    MOV DL,0AH        ;显示换行
    MOV AH,2
    INT 21H
    MOV BL,TI
    ADD BL,TJ
    MOV J,BL    ;保留列数据
    MOV TI,0
    MOV TJ,0    ;初始化,防止数据遗留出问题
    ;获得相对MP的8位/16位的偏移量
    MOV AL,I    ;行数*15+列数才是当前元素的相对于MP的偏移量
    MOV CL,15
    MUL CL
    MOV CNTW,AX
    MOV DL,BYTE PTR CNTW
    ADD DL,J
    MOV CNT,DL    ;保留相对于MP的偏移量 为byte
    MOV AX,WORD PTR CNT
    MOV CNTW,AX    ;保留相对于MP的偏移量 为Word
    MOV BX,OFFSET MP
    ADD BX,CNTW
    MOV AL,[BX]    ;取到MP当前玩家准备落子的坐标
    CMP AL,0    ;此处是否没有落子
    JNE folloB    ;不等于0表示此处有落子 该次落子失败,需要重新选择落子处
    ;成功落子,改变MP的坐标数据
    ADD OVERFLOW,1    ;落子+1
    MOV BYTE PTR[BX],12H    ;以W代表白棋  B代表黑棋        因为棋盘输出时每个数字自动加30H转为字符，所以此处的W ASII码先减去30H
    CALL WIN;调用判断获胜与否的子程序
    CMP OVERFLOW,225
    JE OVER_FLOW    ;棋盘满了
    CMP FINISH,1
    JE EXIT        ;获胜条件已达成
    JMP NEAR PTR IN_WHITE        ;游戏没有达到结束条件，继续
EXIT:    ;棋局结束
    CALL SHOW
    MOV AL,NOW            ;NOW是获胜时落子的一方
    CMP AL,27H
    JE WHITE_WIN
    CMP AL,12H
    JE BLACK_WIN
OVER_FLOW:
    CMP FINISH,1    ;最后一子导致五子成线了，由于白子先行必定是白棋落的最后一子，所以此时会白棋胜
    JE WHITE_WIN
    MOV DX,OFFSET STR9
    MOV AH,9
    INT 21h        ;棋盘满了没人获胜，和棋
    JMP GAME_OVER
WHITE_WIN:
    MOV DX,OFFSET STR7
    MOV AH,9
    INT 21h        ;白棋获胜
    JMP GAME_OVER
BLACK_WIN:
    MOV DX,OFFSET STR8
    MOV AH,9
    INT 21h        ;黑棋获胜
    JMP GAME_OVER
GAME_OVER:
    MOV AX,4C00H;退出
    INT 21H
MAIN ENDP

WIN PROC NEAR    ;获胜逻辑判断子程序
    PUSH AX
    PUSH BX
    PUSH CX
    PUSH DX;保存现场
    MOV COUNT,1
    MOV AL,[BX]        ;当前是哪一方落子        这个NOW是W/B的ASII码减去30H的值
    MOV NOW,AL
    MOV CL,J;    从左到右的判断 根据J当前值确定是否退出循环
LEFT_TO_RIGHT_LEFT:        ;从左到右时count不刷新，进入新的一条线五子判断时才需要
    DEC CL         ;往左一个元素
    CMP CL,0    ;小于0则跳入右判断
    JB LEFT_TO_RIGHT_RIGHT
    SUB BX,1        ;BX地址减1
    CMP AL,[BX]
    JNE LEFT_TO_RIGHT_RIGHT    ;与落子的棋子不同
    ADD COUNT,1        ;若相同,一条线上的棋子数+1
    CMP COUNT,5        ;若COUNT达到5，说明有一方已经胜利了
    JE FIVE_TO_WINN    ;此处条件跳转可能超出 需要找个中继继续跳转
    JMP LEFT_TO_RIGHT_LEFT    ;循环继续
LEFT_TO_RIGHT_RIGHT:
    MOV CL,J    ;需要初始化一些数据
    MOV BX,OFFSET MP    ;重新定位到落子坐标
    ADD BX,CNTW
START_1:
    INC CL        ;往右一个元素
    CMP CL,14    ;大于14则跳入下一个判断
    JA UP_TIL_DOWN_UP
    ADD BX,1
    CMP AL,[BX]
    JNE UP_TIL_DOWN_UP    ;不等时跳入下一个判断区间
    ADD COUNT,1        ;若相同,一条线上的棋子数+1
    CMP COUNT,5        ;若COUNT达到5，说明有一方已经胜利了
    JE FIVE_TO_WINN
    JMP START_1        ;循环继续
UP_TIL_DOWN_UP:
    MOV CL,I    ;需要初始化一些数据
    MOV BX,OFFSET MP    ;重新定位到落子坐标
    ADD BX,CNTW
    MOV COUNT,1        ;count初始化
START_2:
    DEC CL         ;往上一个元素
    CMP CL,0    ;小于0则跳入下判断
    JB UP_TIL_DOWN_DOWN
    SUB BX,15        ;BX地址减15
    CMP AL,[BX]
    JNE UP_TIL_DOWN_DOWN    ;与落子的棋子不同
    ADD COUNT,1        ;若相同,一条线上的棋子数+1
    CMP COUNT,5        ;若COUNT达到5，说明有一方已经胜利了
FIVE_TO_WINN:    JE FIVE_TO_WIN
    JMP START_2    ;循环继续
UP_TIL_DOWN_DOWN:
    MOV CL,I    ;需要初始化一些数据
    MOV BX,OFFSET MP    ;重新定位到落子坐标
    ADD BX,CNTW
START_3:
    INC CL         ;往下一个元素
    CMP CL,14    ;大于14则跳入下判断
    JA LEFTUP_TO_RIGHTDOWN_LEFTUP
    ADD BX,15        ;BX地址加15
    CMP AL,[BX]
    JNE LEFTUP_TO_RIGHTDOWN_LEFTUP    ;与落子的棋子不同
    ADD COUNT,1        ;若相同,一条线上的棋子数+1
    CMP COUNT,5        ;若COUNT达到5，说明有一方已经胜利了
    JE FIVE_TO_WIN
    JMP START_3    ;循环继续
LEFTUP_TO_RIGHTDOWN_LEFTUP:        ;左上到右下 左上部分
    MOV CL,I    ;需要初始化一些数据    这是行 数据
    MOV DL,J    ;这是列 数据
    MOV BX,OFFSET MP    ;重新定位到落子坐标
    ADD BX,CNTW
    MOV COUNT,1        ;count初始化
START_4:
    DEC CL         ;往左上一个元素
    DEC DL
    CMP CL,0    ;小于0则跳入下判断
    JB LEFTUP_TO_RIGHTDOWN_RIGHTDOWN
    CMP DL,0
    JB LEFTUP_TO_RIGHTDOWN_RIGHTDOWN
    SUB BX,16        ;BX地址减16
    CMP AL,[BX]
    JNE LEFTUP_TO_RIGHTDOWN_RIGHTDOWN    ;与落子的棋子不同
    ADD COUNT,1        ;若相同,一条线上的棋子数+1
    CMP COUNT,5        ;若COUNT达到5，说明有一方已经胜利了
FIVE_TO_WIN:
    JE FIVE_WINNN
    JMP START_4    ;循环继续
LEFTUP_TO_RIGHTDOWN_RIGHTDOWN:    ;右下部分
    MOV CL,I    ;需要初始化一些数据    这是行 数据
    MOV DL,J    ;这是列 数据
    MOV BX,OFFSET MP    ;重新定位到落子坐标
    ADD BX,CNTW
START_5:
    INC CL        ;往右下一个元素
    INC DL
    CMP CL,14    ;大于14则跳入下一个判断
    JA LEFTDOWN_TO_RIGHTUP_LEFTDOWN
    CMP DL,14
    JA LEFTDOWN_TO_RIGHTUP_LEFTDOWN
    ADD BX,16
    CMP AL,[BX]
    JNE LEFTDOWN_TO_RIGHTUP_LEFTDOWN    ;不等时跳入下一个判断区间
    ADD COUNT,1        ;若相同,一条线上的棋子数+1
    CMP COUNT,5        ;若COUNT达到5，说明有一方已经胜利了
FIVE_WINNN:    JE FIVE_WIN
    JMP START_5        ;循环继续
LEFTDOWN_TO_RIGHTUP_LEFTDOWN:        ;左下到右上 左下部分
    MOV CL,I    ;需要初始化一些数据    这是行 数据
    MOV DL,J    ;这是列 数据
    MOV BX,OFFSET MP    ;重新定位到落子坐标
    ADD BX,CNTW
    MOV COUNT,1        ;count初始化
START_6:
    INC CL         ;往左下一个元素
    DEC DL
    CMP CL,14    ;大于14则跳入下判断
    JA LEFTDOWN_TO_RIGHTUP_RIGHTUP
    CMP DL,0
    JB LEFTDOWN_TO_RIGHTUP_RIGHTUP
    ADD BX,14        ;BX地址加14
    CMP AL,[BX]
    JNE LEFTDOWN_TO_RIGHTUP_RIGHTUP    ;与落子的棋子不同
    ADD COUNT,1        ;若相同,一条线上的棋子数+1
    CMP COUNT,5        ;若COUNT达到5，说明有一方已经胜利了
    JE FIVE_WIN
    JMP START_6    ;循环继续
LEFTDOWN_TO_RIGHTUP_RIGHTUP:    ;右上部分
    MOV CL,I    ;需要初始化一些数据    这是行 数据
    MOV DL,J    ;这是列 数据
    MOV BX,OFFSET MP    ;重新定位到落子坐标
    ADD BX,CNTW
START_7:
    DEC CL        ;往右上一个元素
    INC DL
    CMP CL,0    ;小于0则结束四个获胜判断
    JB OK
    CMP DL,14
    JA OK
    SUB BX,14
    CMP AL,[BX]
    JNE OK
    ADD COUNT,1        ;若相同,一条线上的棋子数+1
    CMP COUNT,5        ;若COUNT达到5，说明有一方已经胜利了
    JE FIVE_WIN
    JMP START_7    ;循环继续
FIVE_WIN:    ;出现了获胜的棋面 结束游戏并显示胜利者
    MOV FINISH,1    ;用这个变量判断胜利
OK:
    MOV COUNT,1
    POP DX        ;结束胜负判断 恢复现场
    POP CX
    POP BX
    POP AX
    RET    ;返回原调用程序
WIN ENDP

SHOW PROC NEAR    ;屏幕显示棋盘子程序
    PUSH AX
    PUSH BX
    PUSH CX
    PUSH DX;保存现场
    MOV DX,OFFSET STR2
    MOV AH,9
    INT 21h
    MOV BX,OFFSET MP    ;当前是第几个元素
    MOV CH,0    ;行数
NICE:
    MOV CL,0    ;列数
    CMP CH,10    ;行数大于等于10不用加空格前缀
    JAE UPTEN
    MOV AH,02H
    MOV DL,' '    ;加空格前缀
    INT 21H
    MOV AH,02H
    MOV DL, CH
    ADD DL,30H
    INT 21H
    MOV AH,02H
    MOV DL, ' '
    INT 21H
    MOV AH,02H
    MOV DL, ' '
    INT 21H
    JMP LINE    ;小于10的数进入打印棋盘
UPTEN:
    MOV AH,02H
    MOV DL,'1'        ;十位
    INT 21H
    MOV AH,02H
    MOV DL,CH
    ADD DL,38    ;个位
    INT 21H
    MOV AH,02H
    MOV DL, ' '
    INT 21H
    MOV AH,02H
    MOV DL, ' '        ;字符间隔调整
    INT 21H
LINE:
    MOV AH, 02H
    MOV DL,[BX]
    ADD DL,30H
    INT 21H
    MOV AH,02H
    MOV DL, ' '
    INT 21H
    MOV AH,02H
    MOV DL, ' '
    INT 21H
    ADD BX,1    ;往后一个元素
    ADD CL,1
    CMP CL,15
    JB LINE        ;当前行未到尾部，继续输出
    MOV DL,0DH
    MOV AH,2
    INT 21H
    MOV DL,0AH        ;显示换行
    MOV AH,2
    INT 21H
    ADD CH,1;显示当前行  自增到下一行
    CMP CH,15
    JAE FIN    ;棋盘显示完毕，结束
    JMP NICE
FIN:
    POP DX        ;结束棋盘输出 恢复现场
    POP CX
    POP BX
    POP AX
    RET    ;返回原调用程序
SHOW ENDP
CSEG ENDS
END START        ;终于写完了！！！